{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../../img/horse-colic.png\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Индивидуальный проект по анализу данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Horse Colic Dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Всем привет, героем сегодняшнего дня станет достаточно интересный датасет на медицинскую тематику, а именно, встречайте Horse Colic Dataset. Сие чудо обитает на [kaggle](https://www.kaggle.com/uciml/horse-colic), куда он в свою очередь пришел с архива датасетов [UCI](https://archive.ics.uci.edu/ml/datasets/Horse+Colic).\n",
    "\n",
    "Основной задачей в данном датасете является предсказание возможной смерти лошади на основе текущего состояния и прошлых заболеваний. Соотвественно ценность решаемой задачи в том, что было бы хорошо иметь модель, предсказывающую исход для конкретной лошади по текущему состоянию, что позволило бы вовремя производить хирургическое вмешательство во имя спасения жизни. Как мы увидим позже, человеку достаточно тяжело сделать точные выводы на основе такого количества медицинских признаков.\n",
    "\n",
    "Если более конкретно, то стоит задача мультиклассовой классификации (3 класса) на типичных медицинских данных: очень мало данных, много признаков и ооочень много пропусков (порядка 30%). Но если пропуски можно заполнить, то проблема количества данных стоит очень остро.\n",
    "\n",
    "Хочу сразу предупредить, что делал ацент именно на EDA, а не на стакинге 10 бустингов и переборе всех моделей подряд, поэтому уберите от экранов людей со слабой психикой. Давайте же приступим..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import absolute_import, division, print_function, unicode_literals\n",
    "from pylab import rcParams\n",
    "\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "import warnings\n",
    "import itertools\n",
    "import scipy.stats as ss\n",
    "\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.model_selection import cross_val_score\n",
    "from sklearn.metrics import make_scorer\n",
    "from sklearn.decomposition import PCA\n",
    "\n",
    "%matplotlib inline\n",
    "warnings.simplefilter('ignore')\n",
    "rcParams['figure.figsize'] = 10, 8\n",
    "sns.set_style('darkgrid')\n",
    "sns.set(font_scale=1.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Загружаем датасет и сразу видим, что большинство признаков категориальные. Чуть ниже я привел расшифровку каждого признака. Так как переводить медицинские термины, будучи далеким от медицины человеком, достаточно тяжело, не исключено, что я где-то ошибся."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "horse_df = pd.read_csv('../../data/horse.csv')\n",
    "horse_df.head(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Описание признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1: **Наличие операций (бинарный признак):**\n",
    "    - 1 = да;\n",
    "    - 2 = нет.\n",
    "\n",
    "2: **Возраст (бинарный признак):**\n",
    "    - 1 = взрослая лошадь;\n",
    "    - 2 = молодая лоашадь (<6 месяцев).\n",
    "\n",
    "3: **Номер больницы (категориальный признак):**\n",
    "    - числовой идентификатор;\n",
    "    - номер дела лошади (может быть не уникальным, если лошадь лечится > 1 раз).\n",
    "    \n",
    "4: **Ректальная температура (количественный признак):**\n",
    "    - в градусах Цельсия;\n",
    "    - повышенная температура может возникнуть из-за инфекции;\n",
    "    - температура может быть уменьшена, когда лошадь находится в шоке;\n",
    "    - нормальная температура составляет 37.8;\n",
    "    - этот параметр обычно растет по мере прогрессирования болезни.\n",
    "    \n",
    "5: **Пульс (количественный признак):**\n",
    "    - частота сердечных сокращений в ударах в минуту;\n",
    "    - отражает состояние сердца: 30-40 норма для взрослых;\n",
    "    - редко бывает меньше нормы, хотя атлетичные лошади могут иметь 20-25 ударов в минуту;\n",
    "    - лошади с травмами или со спазмом сосудов могут иметь повышенную частоту сердечных сокращений.\n",
    "\n",
    "6: **Частота дыхания (количественный признак):**\n",
    "    - нормальная частота составляет от 8 до 10;\n",
    "    - полезность сомнительна из-за больших колебаний.\n",
    "\n",
    "7: **Температура конечностей (категориальный признак):**\n",
    "    - субъективная оценка состояния кровообращения;\n",
    "    - возможные значения:\n",
    "        1 = Normal \n",
    "        2 = Warm\n",
    "        3 = Cool\n",
    "        4 = Cold\n",
    "    - прохладные и холодные конечности указывают на возможный шок;\n",
    "    - горячие конечности должны коррелировать с повышенной ректальной температурой.\n",
    "    \n",
    "8: **Венозный пульс (категориальный признак):**\n",
    "    - возможные значения:\n",
    "        1 = normal \n",
    "        2 = increased (повышенный)\n",
    "        3 = reduced (сниженный)\n",
    "        4 = absent (отсутсвует)\n",
    "    - нормальный или увеличенный пульс указывают на адекватную циркуляцию, в то время как уменьшенный или отсутствующий\n",
    "      указывает на плохое кровоснабжение.\n",
    "\n",
    "9: **Цвет слизистой (категориальный признак):**\n",
    "    - субъективная оценка цвета;\n",
    "    - возможные значения:\n",
    "        1 = normal pink (нормальный розовый)\n",
    "        2 = bright pink (ярко-розовый)\n",
    "        3 = pale pink (бледно-розовый)\n",
    "        4 = pale cyanotic (бледно-цианотический)\n",
    "        5 = bright red / injected  (ярко-красный)\n",
    "        6 = dark cyanotic (темная цианотический)\n",
    "    - 1 и 2 указывают на нормальную или слегка увеличенную циркуляцию\n",
    "    - 3 может произойти в раннем шоке\n",
    "    - 4 и 6 свидетельствуют о серьезном нарушении кровообращения\n",
    "    - 5 больше указывает на септицемию (инфицированеи крови)\n",
    "\n",
    "10: **Время заполнения капилляра (категориальный признак):**\n",
    "    - чем дольше заполнение капилляра, тем хуже циркуляция;\n",
    "    - возможные значения:\n",
    "        1 = <3 секунды\n",
    "        2 = >= 3 секунды\n",
    "        3 = 3 cекунды\n",
    "        \n",
    "11: **Оценка уровня боли (категориальный признак):**\n",
    "    - возможные значения:\n",
    "        1 = alert, no pain (предупреждение, отсутствие боли)\n",
    "        2 = depressed (подавлен)\n",
    "        3 = intermittent mild pain (прерывистая слабая боль)\n",
    "        4 = intermittent severe pain (прерывистая сильная боль)\n",
    "        5 = continuous severe pain (непрерывная сильная боль)\n",
    "    - НЕ следует рассматривать как упорядоченную или дискретную переменную!\n",
    "    - в целом, чем больнее, тем более вероятно, что потребуется хирургическое вмешательство\n",
    "    - предварительное лечение может в какой-то мере замаскировать уровень боли\n",
    "\n",
    "12: **Сократимость кишечника (категориальный признак):**\n",
    "    - указание на активность в кишечнике лошади;\n",
    "    - возможные значения:\n",
    "        1 = hypermotile\n",
    "        2 = normal\n",
    "        3 = hypomotile \n",
    "        4 = absent (отсутсвует)\n",
    "\n",
    "13: **Метеоризм (категориальный признак):**\n",
    "    - ВАЖНЫЙ параметр;\n",
    "    - возможные значения\n",
    "        1 = none (нет)\n",
    "        2 = slight(незначительный)\n",
    "        3 = moderate (умеренный)\n",
    "        4 = severe (тяжелый)\n",
    "    - лошадь с тяжелой стадией метеоризма, требует хирургического вмешательства.\n",
    "    \n",
    "14: **Назогастральная трубка (категориальный признак):**\n",
    "    - возможные значения:\n",
    "        1 = none (нет)\n",
    "        2 = slight (незначительный)\n",
    "        3 = significant (значительный)\n",
    "\n",
    "15: **Рвота (категориальный признак):**\n",
    "    - возможные значения\n",
    "        1 = нет\n",
    "        2 = >1 литра\n",
    "        3 = <1 литра\n",
    "    - чем больше рвота, тем больше вероятность того, что\n",
    "      существует некоторая серьезная непроходимость в кишечнике.\n",
    "      \n",
    "16: **PH Рвоты (количественный признак):**\n",
    "    - шкала от 0 до 14, 7 является нейтральным;\n",
    "    - нормальные значения находятся в диапазоне от 3 до 4.\n",
    "    \n",
    "17: **Исследование фекалий (категориальный признак):**\n",
    "    - возможные значения\n",
    "        1 = normal (нормальный)\n",
    "        2 = increased (повышенный)\n",
    "        3 = decreased (сниженный)\n",
    "        4 = absent (отсутствует)\n",
    "    - отсутствие фекалий, вероятно, указывает на обстипацию.\n",
    "\n",
    "18: **Состояние кишечника (категориальный признак):**\n",
    "    - возможные значения\n",
    "        1 = normal (нормально)\n",
    "        2 = other (другое)\n",
    "        3 = firm feces in the large intestine (твердые фекалии в толстой кишке)\n",
    "        4 = distended small intestine (растянутый тонкий кишечник)\n",
    "        5 = distended large intestine (растянутый толстый кишечник)\n",
    "    - 4 и 5 требуют хирургического вмешательства\n",
    "\n",
    "19: **Объем красных кровяных клеток в крови (количественный признак):**\n",
    "    - количество красных клеток на единицу объема в крови;\n",
    "    - нормальный диапазон от 30 до 50. Уровень повышается по мере того, как нарушается кровообращение или\n",
    "      появляется обезвоживание.\n",
    "\n",
    "20: **Общий белок (количественный признак):**\n",
    "    - нормальные значения лежат в диапазоне 6-7,5 (gms / dL)\n",
    "    - чем выше значение, тем больше обезвоживание\n",
    "    \n",
    "21: **Абдоминоцентез (категориальный признак):**\n",
    "    - возможные значения:\n",
    "        1 = clear\n",
    "        2 = cloudy\n",
    "        3 = serosanguinous\n",
    "    - cloudy и serosanguinous указывает на проблемы с кишечником.\n",
    "    \n",
    "22: **Общий белок абдоминоцентеза (количественный признак):**\n",
    "    - чем выше уровень белка, тем более вероятно, что есть проблемы с кишечником. Значения указаны в gms / dL\n",
    "    \n",
    "23: **Итог (целевой признак):**\n",
    "    - что в итоге случилось с лошадью?\n",
    "    - возможные значения:\n",
    "        1 = выжила\n",
    "        2 = умерла\n",
    "        3 = была подвергнута эвтаназии (облегчение предсмертных страданий неизлечимо больных, умерщвление)\n",
    "        \n",
    "24: **Наличие операционных травм (бинарный признак):**\n",
    "    - возможные значения:\n",
    "        1 = Да\n",
    "        2 = Нет\n",
    "        \n",
    "25, 26, 27: **Типы травм:**\n",
    "    - состоит из 4 цифр;\n",
    "    - первое число - место поражения:\n",
    "        1 = желудок\n",
    "        2 = тонкий кишечник\n",
    "        3 = толстая кишка\n",
    "        4 = толстая кишка и слепая кишка\n",
    "        5 = слепая кишка\n",
    "        6 = поперечная толстая кишка\n",
    "        7 = обратная/нисходящая кишка\n",
    "        8 = матка\n",
    "        9 = мочевой пузырь\n",
    "        11 = все кишечные участки\n",
    "        00 = нет сведений\n",
    "    - второе число - тип поражения:\n",
    "        1 = простое поражение\n",
    "        2 = приступ\n",
    "        3 = воспаление\n",
    "        4 = другое\n",
    "    - третье число - подтип поражения:\n",
    "        1 = механический\n",
    "        2 = паралитический\n",
    "        0 = нет сведений\n",
    "    - четвертое число - спец. код:\n",
    "        1 = обтурация\n",
    "        2 = внутреннее поражение\n",
    "        3 = внешнее поражение\n",
    "        4 = адинамическое поражение\n",
    "        5 = заворот кишок\n",
    "        6 = поражение инвагинации\n",
    "        7 = тромбоэмболическое поражение\n",
    "        8 = грыжа\n",
    "        9 = липома\n",
    "        10 = смещение\n",
    "        0 = нет сведений\n",
    "\n",
    "28: **Патология (бинарный признак):**\n",
    "    - имеeтся ли патология?\n",
    "        1 = Да\n",
    "        2 = Нет"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Бросим беглый взгляд на пропуски всего датасета и увидим, что больше половины признаков таковые имеют. Дальше вернемся к ним более подробно. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "horse_df.isnull().any()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сразу отделим признаки от целевой переменной, которая носит название outcome"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = horse_df[horse_df.columns.difference(['outcome'])]\n",
    "y = horse_df['outcome']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Объектов в датасете: {0}\\nПризнаков: {1}'.format(X.shape[0], X.shape[1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "А вот и численное подтверждение малочисленности данных, я бы даже сказал катострафической малочисленности. Невольно возникает вопрос, можно ли вообще выжать что-то хороше из них."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разделим датасет на категориальные и количественные признаки, так дальше будет легче работать"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "numeric_features = ['rectal_temp', 'pulse', 'respiratory_rate', 'nasogastric_reflux_ph',\n",
    "                    'packed_cell_volume', 'total_protein', 'abdomo_protein']\n",
    "categorial_features = X.columns.difference(numeric_features)\n",
    "\n",
    "X_numeric = X[numeric_features]\n",
    "X_categorial = X[categorial_features]\n",
    "\n",
    "print('Категориальных признаков: {0}\\nКоличественных признаков: {1}'.format(X_categorial.shape[1], X_numeric.shape[1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Итак, входные данные: 299 объектов, 7 числовых признаков и 20 категориальных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Описание целевой переменной"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим небольшой counplot и посмотрим, какие есть классы и в каком они количестве"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print((round(y.value_counts(normalize=True), 2) * 100).map(lambda x: '{0}%'.format(str(int(x)))))\n",
    "sns.countplot(y);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Что ж, есть три класса: лошадь выжила, лошадь погибла и лошадь усыпыли, чтобы облегчить предсмертные страдания. Отсюда можно сделать первые выводы, классы died и euthanized очень похожи и по сути имеют одинаковый итог, поэтому велика вероятность, что их будет тяжело отделить друг от друга и они будут перемешиваться. По идее их можно объединить, но мы оставим как это задумывалось.\n",
    "\n",
    "Второе, это то, что необходимо будет как-то сбалансировать классы. Например применить oversampling или выставить параметр weighted в моделях.\n",
    "\n",
    "Третье, подобрать соответсвующую метрику, учитывающую дисбаланс классов."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Далее, так как признаков для анализа очень много, проанализируем количественные и категориальные признаки по отдельности."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Анализ количественных признков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Убедимся, что все признаки количественные, кроме того уже виден масштаб пропущенных данных."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Новая информация = новые выводы: стоит попробовать заполнить пропуски средним, медианой или более умными методами."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на статистические характеристики"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Можно косвенно говорить о наличии выбросов, если min и max по столбцам отличается от mean больше чем на порядок:\n",
    "- rectal_temp:        все нормально\n",
    "- pulse:              max отличается на порядок\n",
    "- respiratory_rate:   min отличается на порядок\n",
    "- nasogastric_ph:     все нормально\n",
    "- packed_cell_volume: все нормально\n",
    "- total_protein:      min отличается на порядок\n",
    "- abdomo_protein:     min отличается на порядок"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Стоит отметить, что нету каких-то экстремальных или кардинально неверных значений. Данные попались достаточно качественные, дальше мы это увидим.\n",
    "\n",
    "Есть несколько вариантов, как работать с выбросами, от просто исключения и индексации странного события, до кросс-валидации с исключением выбросов на стадии оценки. Скорее всего ничего с ними делать не будем, так как данных мало, чтобы их исключать, но и не так много, чтобы заморачиваться с хитрой кросс-валидацией. На изображениях ниже, это будет видно."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим распределение для каждого признака по классам"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric_y = X_numeric.copy()\n",
    "X_numeric_y['outcome'] = y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(font_scale=1.5)\n",
    "rcParams['figure.figsize'] = 20, 15\n",
    "subplot_position = 330\n",
    "for column in X_numeric_y.columns.difference(['outcome']):\n",
    "    \n",
    "    lived = X_numeric_y.loc[X_numeric_y['outcome'] == 'lived']\n",
    "    died = X_numeric_y.loc[X_numeric_y['outcome'] == 'died']\n",
    "    euthanized = X_numeric_y.loc[X_numeric_y['outcome'] == 'euthanized']\n",
    "    subplot_position += 1\n",
    "    plt.subplot(subplot_position)\n",
    "    sns.distplot(lived[column].dropna(), hist=False, rug=True, label='lived', kde_kws={\"shade\": True})\n",
    "    sns.distplot(died[column].dropna(), hist=False, rug=True, label='died', kde_kws={\"shade\": True})\n",
    "    if (column != 'abdomo_protein'):\n",
    "        sns.distplot(euthanized[column].dropna(), hist=False, rug=True, label='euthanized', kde_kws={\"shade\": True})\n",
    "    if (column == 'total_protein'):\n",
    "        plt.ylim(0, 0.03)\n",
    "    plt.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "К сожалению распределение euthanized для abdomo_protein вывести не получилось из-за малого количества данных.\n",
    "\n",
    "Первое, что бросается в глаза, это то, что распределения отдаленно напоминают нормальное распределение (извините за тавтологию), что уже является хорошим знаком. Также присутствуют парочка логнормальных, которые стоит привести к нормальному.\n",
    "\n",
    "Второе, как и ожидалось, с точки зрения количественных признаков, класс lived хорошо отличим, а два других очень похожи друг на друга."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Давайте проверим являются ли распределения нормальными с помощью критерия Шапиро-Уилка и посмотрим поможет ли логарифмирование."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.stats import shapiro\n",
    "\n",
    "for column in X_numeric.columns:\n",
    "    stat, p_value = shapiro(X_numeric[column].dropna())\n",
    "    stat_log, p_value_log = shapiro(np.log(X_numeric[column].dropna()))\n",
    "    \n",
    "    print('{0}: ({1}, {2}) ==> ({3}, {4})\\n'.format(column,\n",
    "                                                           round(stat, 3),\n",
    "                                                           round(p_value, 4),\n",
    "                                                           round(stat_log, 3),\n",
    "                                                           round(p_value_log, 4)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Что ж, ожидаемо, ни одно из распределений не является нормальным (< 0.05) ни до, ни после логарифмирования, но с логарифмированием дела в некоторых случаях явно идут на поправку."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для разнообразия применим менее формальный метод, Q-Q график для признака pulse."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import statsmodels.api as sm\n",
    "pulse = X_numeric['pulse']\n",
    "log_pulse = np.log(X_numeric['pulse'])\n",
    "qq = sm.qqplot(pulse, loc=pulse.mean(), scale=pulse.std())\n",
    "plt.title('Pulse');\n",
    "qq_log = sm.qqplot(log_pulse, loc=log_pulse.mean(), scale=log_pulse.std())\n",
    "plt.title('Log Pulse');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для нормального распределения он будет выглядеть как ровная диагональная линия, и визуальные отклонения интуитивно понятны. Q-Q график признака после логарифмирования показывает, что пролагорифмировать определенно стоит."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим boxplot для каждого признака без привязки к классам. И снова сделаем выводы.\n",
    "\n",
    "Первое, признаки имеют разный масштаб, что может быть критично для линейных моделей.\n",
    "\n",
    "Второе, как и отмечалось ранее, выбросов не очень много (не больше 10 на признак), что с натяжкой позволяет игнорировать их в условиях объема датасета."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(font_scale=1.7)\n",
    "sns.boxplot(data=X_numeric, orient='h');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим диаграммы рассеяния попарного взаимодействия признаков по классам"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "numeric_features_product = list(itertools.product(X_numeric.columns, X_numeric.columns))\n",
    "unique_numeric_features_product = list()\n",
    "for i in range(X_numeric.shape[1]):\n",
    "    for j in range(X_numeric.shape[1]):\n",
    "        if (i > j):\n",
    "            unique_numeric_features_product.append(numeric_features_product[i * X_numeric.shape[1] + j])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f, axarr = plt.subplots(5, 5)\n",
    "f.tight_layout() \n",
    "colors = X_numeric_y['outcome'].map({'lived':'r', 'died':'b', 'euthanized':'g'})\n",
    "\n",
    "for i in range(5):\n",
    "    for j in range(5):\n",
    "        pair_features = unique_numeric_features_product[5*i + j]      \n",
    "        axarr[i, j].scatter(X_numeric_y[pair_features[0]], X_numeric_y[pair_features[1]], c=colors)\n",
    "        axarr[i, j].set_xlabel(pair_features[0], fontsize=13)\n",
    "        axarr[i, j].set_ylabel(pair_features[1], fontsize=13)\n",
    "        if ((i==4) and (j==0)):\n",
    "            break\n",
    "\n",
    "#sns.set(font_scale=1)\n",
    "#subplot_position = 0\n",
    "#for pair_features in unique_numeric_features_product:\n",
    "#    pair_columns = X_numeric[[pair_features[0], pair_features[1]]]\n",
    "#    pair_columns = pair_columns.dropna()\n",
    "#    subplot_position += 1\n",
    "#    plt.subplot(5, 5, subplot_position)\n",
    "#    sns.regplot(x=pair_features[0], y=pair_features[1], data=pair_columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На глаз можно заметить небольшую корреляцию в некоторых признаках. Например pulse-respiratory_rate имеют вполне естественное объяснение, чем больше пульс, тем чаще частота дыхания. Или зависимость pulse-packed_cell_volume, объясняющаяся увеличением скорости кровтока в зависимости от пульса. Но так же есть те, где я затрудняюсь объяснить взаимосвязь, например total_protein с остальным признаками, который неплохо отделяет died от euthanized.\n",
    "\n",
    "В итоге, можно приметить пару кандидатов на формирование новых признаков, но сначала стоит убедиться в этом с помощью корреляции."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Строить корреляцию с целевым признаком, являющимся категорией, не будем, это бессмысленно. А вот для остальных посроим корреляцию Спирмена, так как корреляцию Пирсона стоит использовать для нормальных распределений, коих у нас не наблюдается. Кроме того Спирмен устойчив к выбросам и нелинейным зависимостям."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rcParams['figure.figsize'] = 15, 10\n",
    "sns.set(font_scale=1.5)\n",
    "import numpy as np\n",
    "\n",
    "mask = np.zeros_like(X_numeric.corr())\n",
    "mask[np.triu_indices_from(mask)] = True\n",
    "sns.heatmap(X_numeric.corr(method='spearman'), annot=True, linewidths=.5, mask=mask, cmap='RdBu_r');\n",
    "plt.xticks(rotation=20);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Все те же кандидаты, стоит попробовать построить на их основе новые признаки, так как в их пространстве объекты неплохо разделяются. Также стоит применить PCA abdomo_protein-total_protein, сформировав новый признак и убрав высокую скоррелированность одной природы."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Далее взглянем на зависимость количественных признаков от целевой переменной. Так как корреляции Пирсона и Спирмена между ними по своей сути бессмысленна. Посмотрим на среднее по классам для каждого признака, это даст легко интерпретируемые результаты."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric_y.groupby('outcome').mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По итогу есть явный аутсайдер, rectal_temp скорее всего не поможет в классификации, чего не скажешь о pulse и total_protein. Остальные кажутся также полезными, но в меньшей степени. rectal_temp скорее всего выкинем из модели."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На этом анализ количественных признаков подошел к концу, перейдем к категориальным"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Анализ категориальных признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на категориальные признаки со стороны. Видим опять большое количество пропусков, а также три признака lesion_1, lesion_2, lesion_3, которые на данный момент являются числовыми. Они состоят из 4 цифр, характеризующие полученные травмы, по категориям. Что это за категории есть описание выше."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_categorial.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " На данный момент я исключу их из анализа, потому что им нужная особая предобработка, вернемся к ним позже"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_categorial_y = X_categorial.copy()\n",
    "X_categorial_y['outcome'] = y\n",
    "X_categorial_y_without_lesions = X_categorial_y[X_categorial_y.columns.difference(['lesion_1', 'lesion_2', 'lesion_3'])]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для каждого оставшегося признака построим factorplotы в зависимости от класса, так мы визуально узнаем что там за категории и в каком они соотношении"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "factorplot_columns = X_categorial_y_without_lesions.columns.difference(['outcome', 'hospital_number'])\n",
    "for i in range(0, 16, 2):\n",
    "    fig = plt.figure()\n",
    "    ax1 = fig.add_subplot(121)\n",
    "    ax2 = fig.add_subplot(122)\n",
    "    \n",
    "    g=sns.factorplot(x='outcome', hue=factorplot_columns[i], data=X_categorial_y_without_lesions, kind='count', ax=ax1);\n",
    "    g1=sns.factorplot(x='outcome', hue=factorplot_columns[i+1], data=X_categorial_y_without_lesions, kind='count', ax=ax2);\n",
    "    plt.close(g.fig)\n",
    "    plt.close(g1.fig)\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Окинунов рисунки взглядом, можно уже сейчас заметить некоторые зависимости от целевой переменной. Например взрослые лошади чаще выживают, чем молодые и т.д. Но здесь рисунки представлены больше для ознакомпления с категорями, нежели для проводения анализа с целевой переменной. Кроме того, здесь можно найти кандидатов на объединение, где частота категории мала по сравнению с другими. Я нашел только одну такую, \"3\" в capillary_refill_time, но наверное лучше стоит это делать основываясь на статистике, а не \"на глаз\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Давайте взглядом посмотри на каждый категориальный признак в зависимости от целевой переменной, это будет долго)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='abdomen', hue='outcome', data=X_categorial_y_without_lesions, \n",
    "              order=['normal', 'other', 'firm', 'distend_small', 'distend_large']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Первым идет abdomen, который описывает состояние кишечника. В информации о нем написано, что на крайних стадиях distend_small и distend_large требуется хирургическое вмешательство и как раз на этих же стадиях резко ворзрастает процент умерших и усыпленных лошадей. Совпадение? Я так не думаю."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='abdominal_distention', hue='outcome', data=X_categorial_y_without_lesions,\n",
    "              order=['none', 'slight', 'moderate', 'severe']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Второй признак на очереди abdominal_distention, который характеризует степень метеоризма. Также как и в предыдущем, две последние стадии требуют хирургического вмешательства и опять именно на них возрастает процент погибших лошадей, кроме того, количество погибших начинает превышать количество выживших."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='abdomo_appearance', hue='outcome', data=X_categorial_y_without_lesions,\n",
    "              order=['clear', 'cloudy', 'serosanguious']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "abdomo_appearance - третий признак имеющий отношение к брюшной полости. Не силен в медицине, но ветеринары с помощью шприца получают какую-то жидкость из брюшой полости и смотрят на её \"внешний вид\". Если она чистая, то все хорошо, иначе проблемы. В данном случае процент погибших лошадок плавно увеличивается, а на последней стадии и вовсе превосходит количество выживших."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='age', hue='outcome', data=X_categorial_y_without_lesions,\n",
    "              order=['young', 'adult']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Тут все просто, это возраст лошади. Сильно бросатеся в глаза то, что взрослые выживаю гораздо чаще, чем умирают. Видимо это связано с тем, что до 6 мясецев у лошадей организм еще слишком слабый."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='capillary_refill_time', hue='outcome', data=X_categorial_y_without_lesions,\n",
    "              order=['less_3_sec', '3', 'more_3_sec']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Этот признак дословно можно перевести как \"время заполнения каппиляра\". Где, как и что замеряют не понятно, но он имеет достаточно ясный смысл: чем меньше время заполения, тем более здоровая кровь. Видно что при more_3_sec, количество смертей превышает количество выживших."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='cp_data', hue='outcome', data=X_categorial_y_without_lesions);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Признак патологии, первый кандидат, который стоит выкинуть из рассмотрения. Ничего осмысленнго по нему сказать нельзя, отсюда видно только то, что лошадей без паталогий больше."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='mucous_membrane', hue='outcome', data=X_categorial_y_without_lesions,\n",
    "              order=['normal_pink', 'bright_pink', 'pale_pink', 'pale_cyanotic', 'bright_red', 'dark_cyanotic']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Это визуальная оценка цвета слизистой, которая говорит о состоянии крови. Последние 3 стадии характеризуют серьезное нарушение кровообращения, а bright_red к тому же говорит об инфицировании. Именно на 3 последних стадиях смертность превышает выживаемость, а шансы лошади резко падают."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='nasogastric_reflux', hue='outcome', data=X_categorial_y_without_lesions,\n",
    "              order=['none', 'less_1_liter', 'more_1_liter']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Признак рвоты или соплей, если честно сам понять до конца не могу, но он достаточно легко интепретируется: если есть хоть какое-то количество жидкости, неважно, больше или меньше литра, то шансы выжить сразу же падают. Грубо говоря, можно создать новый признак, вообще наличия обратного тока соплей/рвоты."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='nasogastric_tube', hue='outcome', data=X_categorial_y_without_lesions);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Таже история, непонятно что это за медицинский термин, но он также легко интерпритируется. Характеризует степени значительности, чем более значителен признак, тем больше шансов быть мертвым. Вот так."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='pain', hue='outcome', data=X_categorial_y_without_lesions,\n",
    "              order=['alert', 'depressed', 'mild_pain', 'severe_pain', 'extreme_pain']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Субъективная оцнека боли лошади. Кажется, что он должен быть достаточно показательным и действительно, чем сильнее боль, которую ощущает лошадь, тем больше шансов на смерть"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='peripheral_pulse', hue='outcome', data=X_categorial_y_without_lesions,\n",
    "              order=['absent', 'reduced', 'normal', 'increased']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Замер пульса, видимо по какой-то вене, как у человека на запястье. Повышенный пульс редко регестрируют, так же редко не могут прощупать пульс. В случае пониженного пульса характерно то, что увеличивается смертность, возможно показывает медленное угасание организма."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='peristalsis', hue='outcome', data=X_categorial_y_without_lesions,\n",
    "              order=['absent', 'normal', 'hypomotile', 'hypermotile']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Осталось немного, еще чуть-чуть) Тут сложно понять, что вообще значат эти категории признака \"сократимость\", но при отсутствии сократимости, шансы выжить становятся 50 на 50, в остальных случаях лошади в основном выживают"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='rectal_exam_feces', hue='outcome', data=X_categorial_y_without_lesions,\n",
    "              order=['absent', 'decreased', 'normal', 'increased']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь у нас ректальное обследование фекалий, дословно показывает их \"количество\". При отсутсвии или повышении фикалий шансы выжить по сравнению со смертью сильно уменьшаются, что ясно указывает на проблемы с кишечником"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='surgery', hue='outcome', data=X_categorial_y_without_lesions);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "surgery показывает было ли у лошади в прошлом хирургическо вмешательство. Не трудно заметить, что наличие такого повышает шансы смерти, видимо операция все ухудшает в долгосрочной преспективе."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='surgical_lesion', hue='outcome', data=X_categorial_y_without_lesions);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Схожый признак, но показывает наличие травмы при хирургческом вмешательстве. В целом аналогичная ситуация с предыдущим."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='temp_of_extremities', hue='outcome', data=X_categorial_y_without_lesions,\n",
    "              order=['normal', 'warm', 'cool', 'cold']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Наконец последний категориальный признак - температура конченостей. Если коротко, то при прохладных и холодных конечностях повышаются шансы смерти, характеризует проблемы с кровообращением"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как говорилось выше, корреляция Пирсона и Спирмена для категориальных и бинарных признаков бесмысленна. Значит нужно использовать более подходящий коэффициент, таковым например является коэффициент Крамера или коэффициент Мэтьюса для бинарных. На самом деле есть еще куча всяких, советую хотя бы узнать какие есть. Построим матрицу аналогичную корреляционной и посмотрим, что у нас получилось."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cramers_corrected_stat(confusion_matrix):\n",
    "    chi2 = ss.chi2_contingency(confusion_matrix)[0]\n",
    "    n = confusion_matrix.sum().sum()\n",
    "    phi2 = chi2/n\n",
    "    r,k = confusion_matrix.shape\n",
    "    phi2corr = max(0, phi2 - ((k-1)*(r-1))/(n-1))    \n",
    "    rcorr = r - ((r-1)**2)/(n-1)\n",
    "    kcorr = k - ((k-1)**2)/(n-1)\n",
    "    return np.sqrt(phi2corr / min( (kcorr-1), (rcorr-1)))\n",
    "\n",
    "categorial_cramers = np.zeros((X_categorial_y_without_lesions.shape[1], X_categorial_y_without_lesions.shape[1]))\n",
    "\n",
    "for i in range(len(X_categorial_y_without_lesions.columns)):\n",
    "    for j in range(len(X_categorial_y_without_lesions.columns)):\n",
    "        \n",
    "        confusion_matrix = pd.crosstab(X_categorial_y_without_lesions[X_categorial_y_without_lesions.columns[i]],\n",
    "                                       X_categorial_y_without_lesions[X_categorial_y_without_lesions.columns[j]])\n",
    "        categorial_cramers[i, j] = cramers_corrected_stat(confusion_matrix)\n",
    "\n",
    "categorial_cramers = pd.DataFrame(categorial_cramers)\n",
    "categorial_cramers.columns = X_categorial_y_without_lesions.columns\n",
    "categorial_cramers.index = X_categorial_y_without_lesions.columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = np.zeros_like(categorial_cramers)\n",
    "mask[np.triu_indices_from(mask)] = True\n",
    "sns.heatmap(categorial_cramers, annot=True, linewidths=.5, mask=mask, fmt='.2f');\n",
    "plt.xticks(rotation=70);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "А получилось неплохо и хорошо согласуется с визуальным анализом. cp_data - outcome получил самый низкий коэффициент, о чем собственно мы и говорили."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Давайте посмотрим на межкатегориальное взаимодействие признаков, но спешу вас успокоить, посмотрим лишь самые интересные случаи, в соответствии с корреляционной матрицей."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ct = pd.crosstab(X_categorial['abdomen'], X_categorial['surgical_lesion'])\n",
    "ct = ct.loc[['normal', 'other', 'firm', 'distend_small', 'distend_large'], :]\n",
    "ct.plot.bar(stacked=True);\n",
    "plt.xticks(rotation=0);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Взимосвзяь состояния кишечника и травмы после хирургического вмешательства. Видно, что два самых тяжелых состояния кишечника в большинстве случаев проиходят из-за травм во время операции"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ct = pd.crosstab(X_categorial['pain'], X_categorial['surgical_lesion'])\n",
    "ct = ct.loc[['alert', 'depressed', 'mild_pain', 'severe_pain', 'extreme_pain'], :]\n",
    "ct.plot.bar(stacked=True);\n",
    "plt.xticks(rotation=0);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь у нас взаимодействие субъективной оценки боли и трамы после операции. Таже картина, лошадь испытывает сильные боли, при наличинии травм во время операции. По всей видимости врачам нужно быть аккуратней"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ct = pd.crosstab(X_categorial['abdominal_distention'], X_categorial['surgical_lesion'])\n",
    "ct = ct.loc[['none', 'slight', 'moderate', 'severe'], :]\n",
    "ct.plot.bar(stacked=True);\n",
    "plt.xticks(rotation=0);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И тут все таже травма после операции и вздутие живота. Опять тоже самое, хирургическоя травма приводит к последущим проблемам с животом. Врачам точно стоит быть поаккуратней"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ct = pd.crosstab(X_categorial['mucous_membrane'], X_categorial['capillary_refill_time'])\n",
    "ct = ct.loc[['normal_pink', 'bright_pink', 'pale_pink', 'pale_cyanotic', 'bright_red', 'dark_cyanotic'], :]\n",
    "ct.plot.bar(stacked=True);\n",
    "plt.xticks(rotation=0);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как я отметил ранее, цвет слизистой говорит о состянии крови и кровообращения. В данном случае при нарушениях кровообращения наблюдается загустевание крови, что впринципе логично."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ct = pd.crosstab(X_categorial['abdominal_distention'], X_categorial['capillary_refill_time'])\n",
    "ct = ct.loc[['none', 'slight', 'moderate', 'severe'], :]\n",
    "ct.plot.bar(stacked=True);\n",
    "plt.xticks(rotation=0);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь странная зависимость, чем больше раздут живот тем больше густеет кровь. Я затрудняюсь логически связать эти два факта вместе"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На этом анализ категориальных признаков и в целом признаков подходит к концу. На самом деле стоило бы еще изучить взаимодействие категориальных и количественных признаков, но в данном случае это уже небольшой оверхед для текущей задачи и наверное больше пригодится в каггле или в реальном проекте."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Выбор метрики и модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сначала касательно метрики, из анализа были сделаны выводы, что необходима метрика которая учитывает дизбаланс классов. Accuracy сразу же отметается из рассмотрения и остаются roc-auc, precision, recall и f-мера. Roc-auc не очень удобен в мультиклассовой классификации, да и оставшиеся метрики смотрятся более выгодно в свете медицинской задачи. Так как у нас задачи \"предсказать\" смерть, то соответсвенно нам необходимо определить как можно больше лошадей, которым требуется помощь. Precision просим на выход) Остается recall и f-мера. Просто recall не стоит использовать, так как нужна модель, которая не будет отправлять повально всех на операцию, важна хотя бы какая-то точность. Тут то и подходит f-мера, но слегка модифицированная. F beta, где коэффициент beta > 1 делает акцент на recall, но и не забывает про precision, а это именно то что нам нужно. Кроме того поставим average='micro', так как классы имеют для нас разную ценность и выделить died предпочтительней. С метрикой определились: fbeta(beta=2, average='micro')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Далее модель, есть пару вариантов: ближайшие соседи, линеные модели, деревянные модели и нейронный сети. Первый выбросим, так как в большинстве случаев метод ближайших соседей справляется хуже всех остальных. Последний вариант это как бить из пушки по воробьям, классические методы в таких задачах будут не хуже, а может даже и лучше. Остаются линейные и деревянные, но взглянув на данные, с таким количеством категориальных признаков сразу ясно кому стоит отдать предпочтение, а именно бустингу над деревьями. К тому же кого мы обманываем, это золотой молоток, если не знаешь, что делать, бери бустинг) Модель: lightgbm от Майкрософта."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Создание бейзлайна"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сначала сделаем самый простой бейзлан, от которого уже будем плясать. Самое простое, что мы можем сделать, это спрогнозировать всем смерть и отправить на операцию, тогда мы точно не ошибемся."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics import fbeta_score\n",
    "from sklearn.metrics import classification_report\n",
    "\n",
    "_, X_numeric_test, _, y_test = train_test_split(X_numeric, y, test_size=0.25, random_state=7)\n",
    "y_num = y_test.map({'lived':1, 'died':2, 'euthanized':3})\n",
    "simple_predictions = np.full((y_num.shape), 2)\n",
    "\n",
    "print('fbeta_score: {0}'.format(round(fbeta_score(y_num, simple_predictions, average='micro', beta=2), 2)))\n",
    "print(classification_report(y_num, simple_predictions))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Получилось 0.24 fbeta как стартовая точка, recall по классу died 100%, все спасены, но сделано это не рационально. Будем улучшать"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Помните мы делали большой анализ данных, рисовали картинки, графики? Ну вот из них можно сделать руками самую простую модель для классификации, своебразное голосование над пеньками."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def predict(answers, thresholds, value):\n",
    "    if value < thresholds[0]:\n",
    "        return answers[0]\n",
    "    elif thresholds[0] <= value < thresholds[1]:\n",
    "        return answers[1]\n",
    "    else:\n",
    "        return answers[2]\n",
    "\n",
    "baseline_num_predictions = pd.DataFrame()\n",
    "baseline_num_predictions['pulse'] = X_numeric_test['pulse'].apply((lambda x: predict([1, 3, 2], [72.58, 84.48], x)))\n",
    "baseline_num_predictions['resp_rate'] = X_numeric_test['respiratory_rate'].apply((lambda x: predict([1, 3, 2], [30.20, 32.08], x)))\n",
    "baseline_num_predictions['reflux_ph'] = X_numeric_test['nasogastric_reflux_ph'].apply((lambda x: predict([3, 1, 2], [4.04, 4.98], x)))\n",
    "baseline_num_predictions['cell_volume'] = X_numeric_test['packed_cell_volume'].apply((lambda x: predict([1, 3, 2], [46.75, 51.65], x)))\n",
    "baseline_num_predictions['total_protein'] = X_numeric_test['total_protein'].apply((lambda x: predict([2, 1, 3], [17.05, 34.65], x)))\n",
    "baseline_num_predictions['abdomo_protein'] = X_numeric_test['abdomo_protein'].apply((lambda x: predict([3, 1, 2], [2.60, 3.50], x)))\n",
    "num_predictions = baseline_num_predictions.mode(axis=1).iloc[:, 0].map(int)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Тут я использую только количественные признаки, так как в случае категориальных не понятно когда лошадь died, а когда euthanized, что портило score при совместном использовании."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('fbeta_score: {0}'.format(round(fbeta_score(y_num, num_predictions, average='micro', beta=2), 2)))\n",
    "print(classification_report(y_num, num_predictions))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Очень неплохо, мы подняли fbeta до 0.48. Можно было придумать еще более сложные правила, но оставим это на откуп моделям."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Далее попробуем применить некоторые гепотезы о данных на практике, а также создадим новые признаки."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Проверка гепотез количественных признаков и генерация новых"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сначала хочу сказать, что для проверки влияние той или иной \"фишки\" необязательно делать боевую модель, ведь достаточно стокового random forest, кросс-валидации и random_state.\n",
    "\n",
    "Разделим датасет на train и test как 3:1. Будем валидироватья на train, потом обучим новую модель на всем train и проверимся на train и test, чтобы зафиксировать возможное переобучение. Этого достаточно, чтобы говорить о эффективности внесенных изменений."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.ensemble import RandomForestClassifier\n",
    "\n",
    "fbeta_scorer = make_scorer(fbeta_score, beta=2, average='micro')\n",
    "X_numeric_train_base, X_numeric_test_base, y_train, y_test = train_test_split(X_numeric,\n",
    "                                                                              y.map({'lived':1, 'died':2, 'euthanized':3}),\n",
    "                                                                              test_size=0.25,\n",
    "                                                                              random_state=7)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Существуют разные методы заполнения пропусков, от простых до более умных. Одним из таких является svd, делающий заполнению пропусков аналогично рекомендательной системе. Написав много кода, не получилось получить хоть какой-то значимой прибавки в случае c svd. Медиана также себя не оправдала. К своему счастью наткнулся на отличную [статью](http://www.machinelearning.ru/wiki/images/9/90/Kayumov_course_work_imputer_methods.pdf) по таким методам, в которой как пример был приведен текущий датасет! Посмотрев на график, я понял, что в данном случае среднее держит планку. Повторюсь, это просто везение, что ребята уже сделали заполнение пропусков для текущего датасета)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric_train = X_numeric_train_base.fillna(X_numeric_train_base.mean())\n",
    "X_numeric_test = X_numeric_test_base.fillna(X_numeric_test_base.mean())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Оформим оценку эффективности изменений в маленькую функцию. Кросс-валидация по 3 фолдам (маленький датасет) в 10 итерациях, что в сумме дает оценку на 30 фолдах. Из-за размеров датасета, оценки очень нестабильные и поидее стоит использовать t-статистику как [здесь](https://habrahabr.ru/company/ods/blog/336168/)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def estimate (X_train, X_test):\n",
    "    cv = list()\n",
    "    for i in range(1, 11):\n",
    "        clf = RandomForestClassifier(min_samples_leaf=10, random_state=i)\n",
    "        cv.extend(cross_val_score(clf, X_train, y_train, cv=3, scoring=fbeta_scorer))\n",
    "    print('fbeta_score_cv: {0}'.format(round(np.mean(cv), 2)))\n",
    "\n",
    "    clf.fit(X_train, y_train);\n",
    "    train_predictions = clf.predict(X_train)\n",
    "    test_predictions = clf.predict(X_test)\n",
    "    \n",
    "    print('fbeta_score_train: {0}'.format(round(fbeta_score(y_train, train_predictions, average='micro', beta=2), 2)))\n",
    "    print('fbeta_score_test: {0}'.format(round(fbeta_score(y_test, test_predictions, average='micro', beta=2), 2)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "estimate(X_numeric_train, X_numeric_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Это наши базовые оценки, переобучения не наблюдается. Качество на cv меньше, так как кросс-валидация дает смещенную оценку, особенно на маленьких датасетках. Дальше применяем изменения по отдельности к этому базовому решению, чтобы увидеть, что зашло, а что нет"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric_train['pulse'] = np.log(X_numeric_train['pulse'])\n",
    "X_numeric_train['respiratory_rate'] = np.log(X_numeric_train['respiratory_rate'])\n",
    "X_numeric_train['packed_cell_volume'] = np.log(X_numeric_train['packed_cell_volume'])\n",
    "X_numeric_train['total_protein'] = np.log(X_numeric_train['total_protein'])\n",
    "X_numeric_train['abdomo_protein'] = np.log(X_numeric_train['abdomo_protein'])\n",
    "\n",
    "X_numeric_test['pulse'] = np.log(X_numeric_test['pulse'])\n",
    "X_numeric_test['respiratory_rate'] = np.log(X_numeric_test['respiratory_rate'])\n",
    "X_numeric_test['packed_cell_volume'] = np.log(X_numeric_test['packed_cell_volume'])\n",
    "X_numeric_test['total_protein'] = np.log(X_numeric_test['total_protein'])\n",
    "X_numeric_test['abdomo_protein'] = np.log(X_numeric_test['abdomo_protein'])\n",
    "\n",
    "estimate(X_numeric_train, X_numeric_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Логарифмирование распределений не оказало никакого влияния, что ожидаемо в случае деревьв. Далее использовать не будем."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Попробуем удалить признак rectal_temp, который оказывал слабое влияние на целевую переменную"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric_train = X_numeric_train_base.fillna(X_numeric_train_base.mean())\n",
    "X_numeric_test = X_numeric_test_base.fillna(X_numeric_test_base.mean())\n",
    "\n",
    "del X_numeric_train['rectal_temp']\n",
    "del X_numeric_test['rectal_temp']\n",
    "\n",
    "estimate(X_numeric_train, X_numeric_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И да, score улучшился, анализ проводили не зря"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Помните приметили кандидатов на на формирование новых признаков? Настало их время"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric_train = X_numeric_train_base.fillna(X_numeric_train_base.mean())\n",
    "X_numeric_test = X_numeric_test_base.fillna(X_numeric_test_base.mean())\n",
    "\n",
    "X_numeric_train['diff_protein'] = X_numeric_train['total_protein'] - X_numeric_train['abdomo_protein']\n",
    "X_numeric_test['diff_protein'] = X_numeric_test['total_protein'] - X_numeric_test['abdomo_protein']\n",
    "\n",
    "estimate(X_numeric_train, X_numeric_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И да, опять в точку. В пространсве этих признаков объекты неплохо отделяются друг от друга, что закономерно положительно вляет на показатели. Далее будем его добавлять"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Еще один новый признак"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric_train = X_numeric_train_base.fillna(X_numeric_train_base.mean())\n",
    "X_numeric_test = X_numeric_test_base.fillna(X_numeric_test_base.mean())\n",
    "\n",
    "X_numeric_train['rate_pulse_relation'] = X_numeric_train['respiratory_rate'] / X_numeric_train['pulse']\n",
    "X_numeric_test['rate_pulse_relation'] = X_numeric_test['respiratory_rate'] / X_numeric_test['pulse']\n",
    "\n",
    "estimate(X_numeric_train, X_numeric_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Тут уже можно поспорить о его эффективности, больше похоже на переобучение. Но все равно оставим его и, если что, уберем его на feature selection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric_train = X_numeric_train_base.fillna(X_numeric_train_base.mean())\n",
    "X_numeric_test = X_numeric_test_base.fillna(X_numeric_test_base.mean())\n",
    "\n",
    "X_numeric_train['cell_per_bit'] = X_numeric_train['packed_cell_volume'] / X_numeric_train['pulse']\n",
    "X_numeric_test['cell_per_bit'] = X_numeric_test['packed_cell_volume'] / X_numeric_test['pulse']\n",
    "\n",
    "estimate(X_numeric_train, X_numeric_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Еще один признак, снова небольшое улучшение. Оставляем"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Два признака total_protein и abdomo_protein, которые вызывали подозрение в высокой корреляции одной природы, попробуем заменть их одним PCA"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric_train = X_numeric_train_base.fillna(X_numeric_train_base.mean())\n",
    "X_numeric_test = X_numeric_test_base.fillna(X_numeric_test_base.mean())\n",
    "\n",
    "pca = PCA(1)\n",
    "pca.fit(X_numeric_train[['total_protein', 'abdomo_protein']])\n",
    "X_numeric_train['pca_protein'] = pca.transform(X_numeric_train[['total_protein', 'abdomo_protein']])\n",
    "X_numeric_test['pca_protein'] = pca.transform(X_numeric_test[['total_protein', 'abdomo_protein']])\n",
    "\n",
    "estimate(X_numeric_train, X_numeric_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сработало, score улучшился. Оставляем, но такой признак тяжело интерпретировать, особенно в медицинской задаче."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Далее тоже самое, но только для категориальных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Проверка гепотез категориальных признаков и генерация новых"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Уберем lesion_1, lesion_2, lesion_3, их добавим чуть позже после предобработки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_categorial_base = X_categorial[X_categorial.columns.difference(['lesion_1', 'lesion_2', 'lesion_3'])]\n",
    "X_categorial_base['hospital_number'] = X_categorial_base['hospital_number'].astype(str)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Тут будем делать немного по-другому, а именно применять изменения ко всему датасету, а потом делить на train и test. Всему виной признак hospital_number, у которого куча категорий. Если делать как с количественными, то у test будет в 2 раза меньше категорий, чем в train, их тяжело состыковать. Ничего криминального в этом нет, но в действительно реальной задаче так лучше не делать.\n",
    "\n",
    "Проверим заполение пропусков самой часто попадающейся категорией."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_categorial_traintest = X_categorial_base.apply(lambda x:x.fillna(x.value_counts().index[0]))\n",
    "X_categorial_traintest = pd.get_dummies(X_categorial_traintest)\n",
    "\n",
    "X_cat_train, X_cat_test, y_train, y_test = train_test_split(X_categorial_traintest,\n",
    "                                                            y.map({'lived':1, 'died':2, 'euthanized':3}),\n",
    "                                                            test_size=0.25,\n",
    "                                                            random_state=7)\n",
    "\n",
    "estimate(X_cat_train, X_cat_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "А теперь оформим пропуски, как новую категорию"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_categorial_traintest = X_categorial_base.fillna('new_category')\n",
    "X_categorial_traintest = pd.get_dummies(X_categorial_traintest)\n",
    "\n",
    "X_cat_train, X_cat_test, y_train, y_test = train_test_split(X_categorial_traintest,\n",
    "                                                            y.map({'lived':1, 'died':2, 'euthanized':3}),\n",
    "                                                            test_size=0.25,\n",
    "                                                            random_state=7)\n",
    "\n",
    "estimate(X_cat_train, X_cat_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Первый случай лучше, оставляем его.\n",
    "\n",
    "Ниже проведем предобработку тех самых lesions и потом сконкатенируем с основным датасетом"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_categorial_traintest = X_categorial_base.apply(lambda x:x.fillna(x.value_counts().index[0]))\n",
    "X_categorial_traintest = pd.get_dummies(X_categorial_traintest)\n",
    "\n",
    "lesion_1 = X_categorial['lesion_1'].astype(str)\n",
    "lesion_2 = X_categorial['lesion_2'].astype(str)\n",
    "lesion_3 = X_categorial['lesion_3'].astype(str)\n",
    "\n",
    "def parse_lesion(x):\n",
    "    if len(x) == 3:\n",
    "        return 'n,n,n,n'\n",
    "    if x == '0':\n",
    "        return 'n,n,n,n'\n",
    "    elif len(x) == 4:\n",
    "        return (','.join(list(x)))\n",
    "    elif len(x) == 6:\n",
    "        return ('{0},{1},{2},{3}'.format(x[:2], x[2], x[3], x[4:]))\n",
    "    else:\n",
    "        if x[:2] == '00':\n",
    "            return ('{0},{1},{2},{3}'.format(x[:2], x[2], x[3], x[4]))\n",
    "        elif x[-2:] != '10':\n",
    "            return ('{0},{1},{2},{3}'.format(x[:2], x[2], x[3], x[4]))\n",
    "        elif x[-2:] == '10':\n",
    "            if x[:2] != '11':\n",
    "                return ('{0},{1},{2},{3}'.format(x[0], x[1], x[2], x[3:]))\n",
    "            else:\n",
    "                return ('{0},{1},{2},{3}'.format(x[:2], x[2], x[3], x[4]))\n",
    "            \n",
    "lesions = pd.DataFrame()\n",
    "for lesion in [lesion_1, lesion_2, lesion_3]:\n",
    "    lesions = pd.concat([lesions, lesion.apply(parse_lesion).str.split(',', expand=True)], axis=1)\n",
    "lesions.columns = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']\n",
    "lesions = pd.get_dummies(lesions)\n",
    "    \n",
    "X_categorial_traintest = pd.concat([X_categorial_traintest, lesions], axis=1)\n",
    "\n",
    "X_cat_train, X_cat_test, y_train, y_test = train_test_split(X_categorial_traintest,\n",
    "                                                            y.map({'lived':1, 'died':2, 'euthanized':3}),\n",
    "                                                            test_size=0.25,\n",
    "                                                            random_state=7)\n",
    "\n",
    "estimate(X_cat_train, X_cat_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Очень неплохо, информация о травмах помогла, оставляем"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_categorial_traintest = X_categorial_base.apply(lambda x:x.fillna(x.value_counts().index[0]))\n",
    "X_categorial_traintest['lesion_abdomen'] = X_categorial_traintest['surgical_lesion'] + X_categorial_traintest['abdomen']\n",
    "X_categorial_traintest = pd.get_dummies(X_categorial_traintest)\n",
    "\n",
    "X_cat_train, X_cat_test, y_train, y_test = train_test_split(X_categorial_traintest,\n",
    "                                                            y.map({'lived':1, 'died':2, 'euthanized':3}),\n",
    "                                                            test_size=0.25,\n",
    "                                                            random_state=7)\n",
    "\n",
    "estimate(X_cat_train, X_cat_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Попытка сгенрировать новый категориальный признак  очень спорная, но стоит поробовать его в деле"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Собираем все воедино"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Все, что было до этого, вело к этому моменту. Делаем полную предобработку со всеми нововведениями"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_numeric_preproc = X_numeric.copy()\n",
    "X_numeric_preproc = X_numeric_preproc.fillna(X_numeric_preproc.mean())\n",
    "\n",
    "del X_numeric_preproc['rectal_temp']\n",
    "X_numeric_preproc['diff_protein'] = X_numeric_preproc['total_protein'] - X_numeric_preproc['abdomo_protein']\n",
    "X_numeric_preproc['rate_pulse_relation'] = X_numeric_preproc['respiratory_rate'] / X_numeric_preproc['pulse']\n",
    "X_numeric_preproc['cell_per_bit'] = X_numeric_preproc['packed_cell_volume'] / X_numeric_preproc['pulse']\n",
    "\n",
    "pca = PCA(1)\n",
    "pca.fit(X_numeric_preproc[['total_protein', 'abdomo_protein']])\n",
    "X_numeric_preproc['pca_protein'] = pca.transform(X_numeric_preproc[['total_protein', 'abdomo_protein']])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_categorial_preproc = X_categorial[X_categorial.columns.difference(['lesion_1', 'lesion_2', 'lesion_3'])].copy()\n",
    "\n",
    "X_categorial_preproc['hospital_number'] = X_categorial_preproc['hospital_number'].astype(str)\n",
    "X_categorial_preproc = X_categorial_preproc.apply(lambda x:x.fillna(x.value_counts().index[0]))\n",
    "X_categorial_preproc['lesion_abdomen'] = X_categorial_preproc['surgical_lesion'] + X_categorial_preproc['abdomen']\n",
    "\n",
    "X_categorial_preproc = pd.get_dummies(X_categorial_preproc)\n",
    "X_categorial_preproc = pd.concat([X_categorial_preproc, lesions], axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_preproc = pd.concat([X_numeric_preproc, X_categorial_preproc], axis=1)\n",
    "X_preproc.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "После всех OHE получили 417 признаков, что достаточно много. Закрадывается мысль сделать PCA, но в медицине необходимы хорошо интерпретируемые признаки, поэтому лучше потом лишние отсеять"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Так как у нас есть готовый преобработанный датасет, давайте взглянем на его вид в двухмерном пространстве"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.manifold import TSNE\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "scaler = StandardScaler()\n",
    "X_scaled = scaler.fit_transform(X_preproc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "tsne = TSNE(random_state=42)\n",
    "tsne_representation = tsne.fit_transform(X_scaled)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter(tsne_representation[:, 0], tsne_representation[:, 1], c=y.map({'lived':'red', 'died':'blue', 'euthanized':'green'}));\n",
    "plt.title('Data Representation')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Что ж, красные точки (lived) можно отделить, чего не скажешь о двух других. Ожидания подтвердились, классы died и euthanized  перемешались и столо бы их слить в один класс еще в самом начале. Но что сделано, то сделано. К тому же, линия между классами явно нелинейная, что наводит на мысль, что линейные модели здесь спасуют, по-крайней без помощи спрямляющих пространств"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter(tsne_representation[:, 0], tsne_representation[:, 1],\n",
    "            c=X_categorial.apply(lambda x:x.fillna(x.value_counts().index[0]))['pain'].map({'alert':'red',\n",
    "                                                                                            'depressed':'blue',\n",
    "                                                                                            'mild_pain':'green',\n",
    "                                                                                            'severe_pain':'orange',\n",
    "                                                                                            'extreme_pain':'yellow'}));\n",
    "plt.title('Pain Category Representation');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Один из высокоскоррелированных признаков с целевой перемнной. В целом объекты неплохо кучкуются, что дает надежду на неплохую классификацию"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter(tsne_representation[:, 0], tsne_representation[:, 1],\n",
    "            c=X_categorial.apply(lambda x:x.fillna(x.value_counts().index[0]))['surgical_lesion'].map({'no':'green',\n",
    "                                                                                                       'yes':'blue'}));\n",
    "plt.title('Surgical Lesion Category Representation');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Образцово-показательный surgical_lesion сходу разделят объекты на два лагеря без всяких вопросов"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter(tsne_representation[:, 0], tsne_representation[:, 1],\n",
    "            c=X_categorial.apply(lambda x:x.fillna(x.value_counts().index[0]))['mucous_membrane'].map({'normal_pink':'red',\n",
    "                                                                                            'bright_pink':'blue',\n",
    "                                                                                            'pale_pink':'green',\n",
    "                                                                                            'pale_cyanotic':'orange',\n",
    "                                                                                            'bright_red':'yellow',\n",
    "                                                                                            'dark_cyanotic':'yellow'}));\n",
    "plt.title('Mucous Membrane Category Representation');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "mucous_mmbrane схож с pain, тоже неплохо"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Делаем модель и отбираем признаки"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сделаем первое небольшое приблежение гиперпараметров с помощью RandomizedSearchCV, чтобы отобрать полезные признаки и убрать мусор"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import lightgbm as lgb\n",
    "from sklearn.model_selection import RandomizedSearchCV\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(X_preproc,\n",
    "                                                    y.map({'lived':1, 'died':2, 'euthanized':3}),\n",
    "                                                    test_size=0.25,\n",
    "                                                    random_state=80)\n",
    "\n",
    "gbm = lgb.LGBMClassifier(boosting_type='gbdt',\n",
    "                         objective='multiclass',\n",
    "                         learning_rate=0.1,\n",
    "                         n_estimators=30,\n",
    "                         min_data=1,\n",
    "                         min_data_in_bin=1)\n",
    "\n",
    "params = {'max_depth': list(range(2, 30, 1)),\n",
    "          'num_leaves': list(range(5, 200, 10)),\n",
    "          'min_child_samples': list(range(5, 100, 10)),\n",
    "          'reg_alpha' : np.arange(1, 10, 1)}\n",
    " \n",
    "rs = RandomizedSearchCV(gbm, params, cv=3, scoring=fbeta_scorer, n_jobs=-1, n_iter=1000, random_state=20)\n",
    "rs.fit(X_train, y_train);\n",
    "print(rs.best_params_)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим их в деле"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gbm = lgb.LGBMClassifier(boosting_type='gbdt',\n",
    "                         objective='multiclass',\n",
    "                         num_leaves=195,\n",
    "                         min_child_samples=85,\n",
    "                         max_depth=9,\n",
    "                         reg_alpha=3)\n",
    "gbm.fit(X_train, y_train)\n",
    "gbm_predictions_train = gbm.predict(X_train)\n",
    "gbm_predictions_test = gbm.predict(X_test)\n",
    "\n",
    "print('fbeta_score_train: {0}'.format(round(fbeta_score(y_train, gbm_predictions_train, average='micro', beta=2), 2)))\n",
    "print('fbeta_score_test: {0}'.format(round(fbeta_score(y_test, gbm_predictions_test, average='micro', beta=2), 2)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Очевидно, что бустинг в этой задаче не далеко ушел от обычно леса, к тому же наблюдается небольшое переобучение, но нам пока достаточно, чтобы отбрать признаки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "feature_importances = dict(zip(X_train.columns, gbm.feature_importances_))\n",
    "feature_importances = sorted(feature_importances.items(), key=lambda x: x[1], reverse=True)[:12]\n",
    "feature_importances"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Нужные признаки по мнению бустинга, остальные равны нулю. Видим, что почти все сгенерированные признаки были по достоинству оценены моделью"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.feature_selection import SelectKBest, f_classif, chi2\n",
    "\n",
    "classif_selector = SelectKBest(f_classif, k=30)\n",
    "classif_selector.fit(X_train, y_train)\n",
    "classif_idxs_selected = classif_selector.get_support(indices=True)\n",
    "X_train_classif = X_train.iloc[:, classif_idxs_selected]\n",
    "\n",
    "list(X_train_classif.columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Также проверили отбор с помощью f_classif. Все признаки бустинга, за исключением парочки, являются подмножеством выбранных f_classif. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "selected_columns = set([x[0] for x in feature_importances]).union(list(X_train_classif.columns))\n",
    "len(selected_columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Просто сделаем объединение и получим 33 самых лучших признака для текущей задачи"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Снова посмотрим на представление датасета в двумерном пространстве"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_selected, X_test_selected = X_train.loc[:, selected_columns], X_test.loc[:, selected_columns]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scaler = StandardScaler()\n",
    "X_scaled = scaler.fit_transform(X_preproc.loc[:, selected_columns])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tsne = TSNE(random_state=42)\n",
    "tsne_representation = tsne.fit_transform(X_scaled)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter(tsne_representation[:, 0], tsne_representation[:, 1], c=y.map({'lived':'red', 'died':'blue', 'euthanized':'green'}));\n",
    "plt.title('Data Representation');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На глаз вроде бы стало получше, но два ненавистных класса по прежнему не хотят разделяться"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Так как данных мало, построим кривые обучения, чтобы оценить стоит ли добавлять данных в датасет. Ванга говорит, что стоит"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import learning_curve\n",
    "from sklearn.pipeline import Pipeline\n",
    "\n",
    "def plot_with_err(x, data, **kwargs):\n",
    "    mu, std = data.mean(1), data.std(1)\n",
    "    lines = plt.plot(x, mu, '-', **kwargs)\n",
    "    plt.fill_between(x, mu - std, mu + std, edgecolor='none',\n",
    "                     facecolor=lines[0].get_color(), alpha=0.2)\n",
    "\n",
    "def plot_learning_curve(X_data, y_data):\n",
    "    train_sizes = np.linspace(0.5, 1, 20)\n",
    "    \n",
    "    pipe = Pipeline([('gbm', lgb.LGBMClassifier(boosting_type='gbdt',\n",
    "                                                objective='multiclass',\n",
    "                                                num_leaves=195,\n",
    "                                                min_child_samples=85,\n",
    "                                                max_depth=9,\n",
    "                                                reg_alpha=3,\n",
    "                                                min_data=1,\n",
    "                                                min_data_in_bin=1))])\n",
    "    \n",
    "    N_train, val_train, val_test = learning_curve(pipe, X_data, y_data,\n",
    "                                                  train_sizes=train_sizes,\n",
    "                                                  cv=3,\n",
    "                                                  scoring=fbeta_scorer,\n",
    "                                                  random_state=15)\n",
    "    plot_with_err(N_train, val_train, label='training scores')\n",
    "    plot_with_err(N_train, val_test, label='validation scores')\n",
    "    plt.xlabel('Training Set Size'); plt.ylabel('fbeta2')\n",
    "    plt.legend()\n",
    "\n",
    "plot_learning_curve(X_preproc.loc[:, selected_columns], y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Наблюдается ужасное переобучение из-за маленького датасета, но кривые сходятся, а это значит, что добавление данных улучшит ситуацию.\n",
    "\n",
    "Сделаем стандратный oversampling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from imblearn.over_sampling import RandomOverSampler \n",
    "over_sampler = RandomOverSampler(random_state=17)\n",
    "X_preproc_sampled, y_sampled = over_sampler.fit_sample(X_preproc.loc[:, selected_columns], y)\n",
    "\n",
    "from sklearn.utils import shuffle\n",
    "X_preproc_sampled, y_sampled = shuffle(X_preproc_sampled, y_sampled, random_state=0)\n",
    "\n",
    "plot_learning_curve(X_preproc_sampled, y_sampled)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Да, ситуация пошла на поправку, но требуются еще данные, которых к сожалению неоткуда взять"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(y_sampled);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Небольшая проверка распределния классов после oversampling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Применим изменения к тренировочной выборке и перемешаем"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_sampled, y_train_sampled = over_sampler.fit_sample(X_train_selected, y_train)\n",
    "X_train_sampled, y_train_sampled = shuffle(X_train_sampled, y_train_sampled, random_state=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Тюнинг гиперпараметров"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Должен сказать lightgbm ведет себя очень нестабильно на маленьких данных, всем на заметку. Ну а пока мы подберем регуляризацию L1, чтобы снизить эффект переобучения"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cross_val_scores = list()\n",
    "train_scores = list()\n",
    "test_scores = list()\n",
    "param_values = list()\n",
    "\n",
    "for param_value in range(1, 21):\n",
    "    cv = list()\n",
    "    for i in range(1, 11):\n",
    "        gbm = lgb.LGBMClassifier(boosting_type='gbdt', objective='multiclass', num_leaves=195, min_child_samples=85,\n",
    "                                 max_depth=9, reg_alpha=param_value, min_data=1, min_data_in_bin=1, random_state=i)\n",
    "        cv.extend(cross_val_score(gbm, X_train_sampled, y_train_sampled, cv=3, scoring=fbeta_scorer))\n",
    "\n",
    "    gbm.fit(X_train_sampled, y_train_sampled);\n",
    "    gbm_train_predictions = gbm.predict(X_train_sampled)\n",
    "    gbm_test_predictions = gbm.predict(X_test_selected)\n",
    "        \n",
    "    cross_val_scores.append(np.mean(cv))\n",
    "    train_scores.append(fbeta_score(y_train_sampled, gbm_train_predictions, average='micro', beta=2))\n",
    "    test_scores.append(fbeta_score(y_test, gbm_test_predictions, average='micro', beta=2))\n",
    "    param_values.append(param_value)    \n",
    "    \n",
    "plt.plot(param_values, cross_val_scores, label='cv')\n",
    "plt.plot(param_values, train_scores, label='train')\n",
    "plt.plot(param_values, test_scores, label='test')\n",
    "plt.legend(loc='upper right')\n",
    "plt.xlabel('reg_alpha'); plt.ylabel('fbeta2')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По кривым валидации улучшение ситуации происходит примерно на 14, его и возьмем"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Дальше еще один косвенный регуляризатор num_leaves"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cross_val_scores = list()\n",
    "train_scores = list()\n",
    "test_scores = list()\n",
    "param_values = list()\n",
    "\n",
    "for param_value in range(2, 15, 1):\n",
    "    cv = list()\n",
    "    for i in range(1, 11):\n",
    "        gbm = lgb.LGBMClassifier(boosting_type='gbdt', objective='multiclass', num_leaves=param_value, min_child_samples=85,\n",
    "                                 max_depth=9, reg_alpha=14, min_data=1, min_data_in_bin=1, random_state=i)\n",
    "        cv.extend(cross_val_score(gbm, X_train_sampled, y_train_sampled, cv=3, scoring=fbeta_scorer))\n",
    "\n",
    "    gbm.fit(X_train_sampled, y_train_sampled);\n",
    "    gbm_train_predictions = gbm.predict(X_train_sampled)\n",
    "    gbm_test_predictions = gbm.predict(X_test_selected)\n",
    "        \n",
    "    cross_val_scores.append(np.mean(cv))\n",
    "    train_scores.append(fbeta_score(y_train_sampled, gbm_train_predictions, average='micro', beta=2))\n",
    "    test_scores.append(fbeta_score(y_test, gbm_test_predictions, average='micro', beta=2))\n",
    "    param_values.append(param_value)    \n",
    "    \n",
    "plt.plot(param_values, cross_val_scores, label='cv')\n",
    "plt.plot(param_values, train_scores, label='train')\n",
    "plt.plot(param_values, test_scores, label='test')\n",
    "plt.legend(loc='upper right')\n",
    "plt.xlabel('num_leaves'); plt.ylabel('fbeta2')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сложно сказать, но я выбрал 6"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Подберем процент случайных признаков, которые будут учавствовать в бэггинге"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cross_val_scores = list()\n",
    "train_scores = list()\n",
    "test_scores = list()\n",
    "param_values = list()\n",
    "\n",
    "for param_value in np.arange(0.1, 1.01, 0.1):\n",
    "    cv = list()\n",
    "    for i in range(1, 11):\n",
    "        gbm = lgb.LGBMClassifier(boosting_type='gbdt', objective='multiclass', num_leaves=6, min_child_samples=85,\n",
    "                                 max_depth=9, reg_alpha=14, min_data=1, min_data_in_bin=1, random_state=i,\n",
    "                                 colsample_bytree=param_value)\n",
    "        cv.extend(cross_val_score(gbm, X_train_sampled, y_train_sampled, cv=3, scoring=fbeta_scorer))\n",
    "\n",
    "    gbm.fit(X_train_sampled, y_train_sampled);\n",
    "    gbm_train_predictions = gbm.predict(X_train_sampled)\n",
    "    gbm_test_predictions = gbm.predict(X_test_selected)\n",
    "        \n",
    "    cross_val_scores.append(np.mean(cv))\n",
    "    train_scores.append(fbeta_score(y_train_sampled, gbm_train_predictions, average='micro', beta=2))\n",
    "    test_scores.append(fbeta_score(y_test, gbm_test_predictions, average='micro', beta=2))\n",
    "    param_values.append(param_value)    \n",
    "    \n",
    "plt.plot(param_values, cross_val_scores, label='cv')\n",
    "plt.plot(param_values, train_scores, label='train')\n",
    "plt.plot(param_values, test_scores, label='test')\n",
    "plt.legend(loc='upper right')\n",
    "plt.xlabel('colsample_bytree'); plt.ylabel('fbeta2')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Странно, но самое адекватное это 100%. Видимо, если бы учавствовали все 417 изначальных признаков, мы бы наблюдали другую картину"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Далее случайный процент объектов, учавствующих в бэггинге"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cross_val_scores = list()\n",
    "train_scores = list()\n",
    "test_scores = list()\n",
    "param_values = list()\n",
    "\n",
    "for param_value in np.arange(0.1, 1.01, 0.1):\n",
    "    cv = list()\n",
    "    for i in range(1, 11):\n",
    "        gbm = lgb.LGBMClassifier(boosting_type='gbdt', objective='multiclass', num_leaves=6, min_child_samples=85,\n",
    "                                 max_depth=9, reg_alpha=14, min_data=1, min_data_in_bin=1, random_state=i,\n",
    "                                 colsample_bytree=1, subsample=param_value)\n",
    "        cv.extend(cross_val_score(gbm, X_train_sampled, y_train_sampled, cv=3, scoring=fbeta_scorer))\n",
    "\n",
    "    gbm.fit(X_train_sampled, y_train_sampled);\n",
    "    gbm_train_predictions = gbm.predict(X_train_sampled)\n",
    "    gbm_test_predictions = gbm.predict(X_test_selected)\n",
    "        \n",
    "    cross_val_scores.append(np.mean(cv))\n",
    "    train_scores.append(fbeta_score(y_train_sampled, gbm_train_predictions, average='micro', beta=2))\n",
    "    test_scores.append(fbeta_score(y_test, gbm_test_predictions, average='micro', beta=2))\n",
    "    param_values.append(param_value)    \n",
    "    \n",
    "plt.plot(param_values, cross_val_scores, label='cv')\n",
    "plt.plot(param_values, train_scores, label='train')\n",
    "plt.plot(param_values, test_scores, label='test')\n",
    "plt.legend(loc='upper right')\n",
    "plt.xlabel('subsample'); plt.ylabel('fbeta2')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Мне кажется 0.6, так именно на таком пороге переобучение сводится до минимума"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создадим финальную модель, на преобработанном датасете, с подобранными гиперпараметрами"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gbm = lgb.LGBMClassifier(boosting_type='gbdt',\n",
    "                         objective='multiclass',\n",
    "                         min_child_samples=85,\n",
    "                         reg_alpha=14,\n",
    "                         num_leaves=6,\n",
    "                         max_depth=9,\n",
    "                         colsample_bytree=1,\n",
    "                         subsample=0.6,\n",
    "                         min_data=1,\n",
    "                         min_data_in_bin=1,\n",
    "                         n_estimators=3000,\n",
    "                         learning_rate=0.1)\n",
    "gbm.fit(X_train_sampled, y_train_sampled)\n",
    "gbm_predictions_train = gbm.predict(X_train_sampled)\n",
    "gbm_predictions_test = gbm.predict(X_test_selected)\n",
    "\n",
    "                         \n",
    "print('fbeta_score_train: {0}'.format(round(fbeta_score(y_train_sampled, gbm_predictions_train, average='micro', beta=2), 2)))\n",
    "print('fbeta_score_test: {0}'.format(round(fbeta_score(y_test, gbm_predictions_test, average='micro', beta=2), 2)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(classification_report(y_test, gbm_predictions_test))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Выводы"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В итоге получилось все достаточно неплохо, удалось улучшить результат. Надо отметить, что это была не борьба за score, а борьба за переобучение, к которому располагает размер датасета. Победить переобучение полностью не получилось, но хотя бы получилось его снизить. Качество на кросс-валидации стабильно ниже, так как 3 фолда для 200 объектов достаточно много, поэтому я бы ориентировался на обычный hold-out.\n",
    "\n",
    "По classification_report видно, что модель находит только 71% из класса died. В реальной задаче я бы не допустил ни одну модель, с recall по классам died и euthanized меньше 95%, к сожалению с таким объемом данных этого почти невозможно.\n",
    "\n",
    "Кроме того меня немного удивило, то что precision соизмерим с recall по всем классам, видимо beta=2 оказалось недостаточно. Ну и тонкой нитью тянувшаеся эпопея с died/euthanized, привела нас к выводу, что их нужно было все-таки объединить."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как улучшить?\n",
    "- основаное это добавить данных\n",
    "- объеденить died/euthanized\n",
    "- усилить вес recall для died/euthanized\n",
    "- отобрать fbeta по необходимому порогу\n",
    "- более глубокие feature engineering и feature selection\n",
    "- более точный подбор гиперпараметров\n",
    "- ансамбль моделей"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
